package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 切面：将通知作用到切入点
 * 对所有业务模块的controller层中使用@GuiGuLogin注解方法进行增强
 *
 * @author: atguigu
 * @create: 2024-10-22 09:24
 */
@Slf4j
@Aspect
@Component
public class LoginAspect {


    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 环绕通知
     * 切入点表达式：第一个 * 代表方法任意返回类型，第二个 * 代表包名，第三个 * 代表类名，第四个 * 代表方法名 .. 代表参数
     *
     * @param pjp        目标方法对象
     * @param guiGuLogin 注解对象
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiGuLogin)")
    public Object doBasicProfiling(ProceedingJoinPoint pjp, GuiGuLogin guiGuLogin) throws Throwable {
        //一、前置通知
        //1. 获取请求头中token令牌值
        //1.1 获取请求对象-获取上层接口
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //1.2 强制转为ServletRequestAttributes 封装请求对象
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        //1.3 获取token
        String token = request.getHeader("token");

        //2. 根据token查询Redis中用户基本信息
        String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
        UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(loginKey);

        //3. 如果目标方法注解的required属性为true，并且用户信息为空，则抛出异常业务状态码设置为208 前端捕获异常，跳转登录页面
        if (guiGuLogin.required() && userInfoVo == null) {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }

        //4. 如果用户信息不为空，则将用户ID存入到ThreadLocal中
        if (userInfoVo != null) {
            Long userId = userInfoVo.getId();
            AuthContextHolder.setUserId(userId);
        }

        //二、执行目标方法调用目标对象target目标方法
        Object retVal = pjp.proceed();

        //三、后置通知
        //5. 避免ThreadLocal内存泄漏导致OOM问题，手动清理ThreadLocal
        AuthContextHolder.removeUserId();

        return retVal;
    }


}
